#include "cocoa/ccocoawindow.h"
#include "cocoa/caglcontext.h"
#include "cocoa/ccocoatoolkit.h"
#include "cocoa/ccocoaexposehelper.h"
#include <buola/gl/copenglgc.h>
#include <buola/gui/cwindowmanager.h>

namespace buola { namespace gui { namespace cocoa {

CCocoaWindow::CCocoaWindow(CWindow *pWindow)
    :   CPhysicalWindow(pWindow)
    ,   mPressedButtons(EMouseButton::NONE)
    ,   mReportedVisibility(false)
{
}

CCocoaWindow::~CCocoaWindow()
{
}

void CCocoaWindow::Create(CPhysicalWindow *pParent,const CPoint_d &pPos,const CSize_d &pSize)
{
    msg_info() << "creating window!! " << (void*)pParent << pPos << pSize << "\n";
    NSRect lRect=NSMakeRect(100,100,pSize.x,pSize.y);
    mNSWindow=[[NSWindow alloc] initWithContentRect: lRect
                                styleMask: NSTitledWindowMask|NSClosableWindowMask|
                                           NSMiniaturizableWindowMask|NSResizableWindowMask
                                backing: NSBackingStoreBuffered
                                defer: NO];
    mContentView=[[CNSView alloc] init:this];
    [mNSWindow setContentView:mContentView ];
    [mNSWindow setAcceptsMouseMovedEvents:YES];
    
    mLastModifiers=CCocoaToolkit::GetMods([NSEvent modifierFlags]);
//    [myWindow setDelegate:theView ];
    mPos={100,100};
    mSize=pSize;

    msg_info() << "created window!!!\n";
}

void CCocoaWindow::Destroy()
{
    ///\todo implement this
}

void CCocoaWindow::Freeze(bool pFrozen)
{
}

void CCocoaWindow::ClearArea(const CRect_i &pR,bool)
{
    ///\todo implement this
}

void CCocoaWindow::Map(bool pRaise)
{
    [mNSWindow orderFront:nil];
}

void CCocoaWindow::Unmap()
{
    [mNSWindow orderOut:nil];
}

void CCocoaWindow::Reparent(CPhysicalWindow*,const CPoint_d&)
{
    ///\todo implement this
}

void CCocoaWindow::AddToSaveSet()
{
    ///\todo implement this
}

void CCocoaWindow::Lower()
{
    ///\todo implement this
}

void CCocoaWindow::Raise(const std::vector<CPhysicalWindow*> &pExc)
{
    ///\todo implement pExc
    [mNSWindow orderFront:nil];
}

void CCocoaWindow::StayOnTop(CPhysicalWindow*)
{
    ///\todo implement this
}

void CCocoaWindow::SelectInput(EEvents pEvents)
{
    ///\todo implement this
}

void CCocoaWindow::SetGravity(int)
{
    ///\todo implement this
}

void CCocoaWindow::SetBackingStore(bool)
{
    ///\todo implement this
}

void CCocoaWindow::SetNoShape(int)
{
    ///\todo implement this
}

void CCocoaWindow::SetShape(const CRegion &pRegion,int,const CPoint_d&)
{
    ///\todo implement this
}

void CCocoaWindow::SetShapeFromMask(img::CSurface *pMask,int,const CPoint_d&)
{
    ///\todo implement this
}

void CCocoaWindow::SetShapeFromRect(const CRect_d&,int,const CPoint_d&)
{
    ///\todo implement this
}

void CCocoaWindow::SetShapeFromWindow(CPhysicalWindow *pWindow,int,const CPoint_d&)
{
    ///\todo implement this
}

void CCocoaWindow::SetPointer(PPointer pPointer)
{
    ///\todo implement this
}

CExposeHelper *CCocoaWindow::GetExposeHelper()
{
    if(!mExposeHelper) mExposeHelper.reset(new CCocoaExposeHelper(this));

    return mExposeHelper.get();
}

void CCocoaWindow::UpdateState()
{
    ///\todo implement this
}

void CCocoaWindow::Embed(CPhysicalWindow *pChild)
{
    ///\todo implement this
}

void CCocoaWindow::GrabButton(EMouseButton pButton,EKeyMod pMods,bool pConfine,CPointer *pCursor)
{
    ///\todo implement this
}

void CCocoaWindow::UngrabButton(EMouseButton pButton,EKeyMod pMods)
{
    ///\todo implement this
}

void CCocoaWindow::Move(const CPoint_d&)
{
    ///\todo implement this
}

void CCocoaWindow::Resize(const CSize_d&)
{
    ///\todo implement this
}

void CCocoaWindow::MoveResize(const CPoint_d&,const CSize_d&)
{
    ///\todo implement this
}

CPoint_d CCocoaWindow::TranslateCoords(const CPoint_d &pPoint,CPhysicalWindow *pWindow)
{
    ///\todo implement this
    return pPoint;
}

void CCocoaWindow::SetFocus()
{
    [mNSWindow makeKeyWindow];
}

void CCocoaWindow::DispatchAll()
{
    ///\todo implement this
}

EWindowState CCocoaWindow::GetState()
{
    ///\todo implement this
    return EWindowState::NONE;
}

void CCocoaWindow::Iconify(bool)
{
    ///\todo implement this
}

void CCocoaWindow::Maximize(EWindowState)
{
    ///\todo implement this
}

void CCocoaWindow::Shade(bool)
{
    ///\todo implement this
}

void CCocoaWindow::Sticky(bool)
{
    ///\todo implement this
}

void CCocoaWindow::SetDesktop(int)
{
    ///\todo implement this
}

void CCocoaWindow::FullScreen(bool)
{
    ///\todo implement this
}

void CCocoaWindow::StartMoveOperation(CMouseEvent *pE)
{
    ///\todo implement this
}

void CCocoaWindow::StartResizeOperation(CMouseEvent *pE,EDragState pType)
{
    ///\todo implement this
}

void CCocoaWindow::SetStrut(const CRect_d &pRect)
{
    ///\todo implement this
}

void CCocoaWindow::SetOpacity(double pOpacity)
{
    ///\todo implement this
}

void CCocoaWindow::SetTitle(const std::wstring &pTitle)
{
    NSString* lTitle = [NSString stringWithUTF8String:utf8(pTitle).c_str()];
    [mNSWindow setTitle:lTitle];
    ///\todo implement this
}

void CCocoaWindow::SetIcon(PIcon pIcon)
{
    ///\todo implement this
}

void CCocoaWindow::UpdateHints()
{
    ///\todo implement this
}

void CCocoaWindow::SendSyntheticExposeEvent(const CRect_d &pRect)
{
    ///\todo implement this
}

void CCocoaWindow::SendSyncRequest()
{
    ///\todo implement this
}

void CCocoaWindow::OnSizeHintsChanged()
{
    ///\todo implement this
}

void CCocoaWindow::OnActivateForeign(bool pActivated)
{
    ///\todo implement this
}

CSize_i CCocoaWindow::SurfaceSize()
{
    return {640,480};
}

img::CGC *CCocoaWindow::GetGC(img::EGCCaps pCaps)
{
    return new gl::COpenGLGC(new CAGLContext(this));
}

void CCocoaWindow::DispatchDrawRect(const CRect_d &pRect)
{
    if(!mReportedVisibility)
    {
        window_manager().OnPhysicalMap(this,true);
        mReportedVisibility=true;
    }
    
    //decide whether it should really receive it
///\todo    if(lXW->GetFlag(CPhysicalWindow::FLAG_RESIZEPENDING)) return;

    window_manager().OnPhysicalExpose(this,pRect,false,false);
    
}

void CCocoaWindow::DispatchGeometry()
{
    CRect_d lRect=nsrect_to_crect_d([mContentView frame]);
    mSize=lRect.Size();
    ///\todo add position
    window_manager().OnPhysicalGeometry(this,mPos,mSize);
}

void CCocoaWindow::DispatchButton(CMouseEvent::EType pType,EMouseButton pButton,NSEvent *pEvent)
{
    CMouseEvent lE;
    lE.mType=pType;

    if(pType==CMouseEvent::PRESS)
    {
        lE.mClickCount=[pEvent clickCount];
        mPressedButtons|=pButton;
    }
    else
    {
        lE.mClickCount=0;
        mPressedButtons&=~pButton;
    }

    lE.mButton=pButton;
    lE.mButtons=mPressedButtons;
    lE.mMods=KEYMOD_NONE;
    lE.mLocks=KEYLOCK_NONE;
    
    NSPoint lPointInWindow=[pEvent locationInWindow];
    NSPoint lPointInView=[mContentView convertPoint:lPointInWindow fromView:nil];
    NSRect lPointInScreen=[mNSWindow convertRectToScreen:NSMakeRect(lPointInWindow.x,lPointInWindow.y,0,0)];
    
    lE.mPoint={lPointInView.x,lPointInView.y};
    lE.mRootPoint={lPointInScreen.origin.x,lPointInScreen.origin.y};

    window_manager().OnPhysicalMouse(&lE,this);
//     ulong timestamp = [theEvent timestamp] * 1000;
// 
//     QCocoaDrag* nativeDrag = static_cast<QCocoaDrag *>(QGuiApplicationPrivate::platformIntegration()->drag());
//     nativeDrag->setLastMouseEvent(theEvent, self);
// 
//     QWindowSystemInterface::handleMouseEvent(m_window, timestamp, qtWindowPoint, qtScreenPoint, m_buttons);
}

void CCocoaWindow::DispatchMotion(NSEvent *pEvent)
{
    CMouseEvent lE;
    lE.mType=CMouseEvent::MOTION;
    
    lE.mMods=KEYMOD_NONE;
    lE.mLocks=KEYLOCK_NONE;
    lE.mButtons=mPressedButtons;
    lE.mClickCount=0;

    ///\todo the X11 backend checks that that is indeed the position of the mouse, and ignores old messages... seems smart
    NSPoint lPointInWindow=[pEvent locationInWindow];
    NSPoint lPointInView=[mContentView convertPoint:lPointInWindow fromView:nil];
    NSRect lPointInScreen=[mNSWindow convertRectToScreen:NSMakeRect(lPointInWindow.x,lPointInWindow.y,0,0)];
    
    lE.mPoint={lPointInView.x,lPointInView.y};
    lE.mRootPoint={lPointInScreen.origin.x,lPointInScreen.origin.y};

    window_manager().OnPhysicalMouse(&lE,this);

}

void CCocoaWindow::DispatchWheel(NSEvent *pEvent)
{
    CMouseEvent lE;
    lE.mType=CMouseEvent::WHEEL;

    lE.mMods=KEYMOD_NONE;
    lE.mLocks=KEYLOCK_NONE;
    lE.mButtons=mPressedButtons;
    lE.mClickCount=0;

    ///\todo the X11 backend checks that that is indeed the position of the mouse, and ignores old messages... seems smart
    NSPoint lPointInWindow=[pEvent locationInWindow];
    NSPoint lPointInView=[mContentView convertPoint:lPointInWindow fromView:nil];
    NSRect lPointInScreen=[mNSWindow convertRectToScreen:NSMakeRect(lPointInWindow.x,lPointInWindow.y,0,0)];

    lE.mPoint={lPointInView.x,lPointInView.y};
    lE.mRootPoint={lPointInScreen.origin.x,lPointInScreen.origin.y};

    msg_info() << "wheel event\n";
    msg_info() << lE.mPoint << lE.mRootPoint << "\n";
    msg_info() << [pEvent deltaX] << " " << [pEvent scrollingDeltaX] << "\n";
    msg_info() << [pEvent deltaY] << " " << [pEvent scrollingDeltaY] << "\n";
    
    lE.mScrollX=[pEvent scrollingDeltaX];
    lE.mScrollY=[pEvent scrollingDeltaY];
    lE.mScrollZ=[pEvent deltaZ];
    
    window_manager().OnPhysicalMouse(&lE,this);
}

void CCocoaWindow::DispatchKey(CKeyEvent::EType pType,NSEvent *pEvent)
{
    NSUInteger lModifierFlags=[pEvent modifierFlags];
    mLastModifiers=CCocoaToolkit::GetMods(lModifierFlags);

    CKeyEvent lE;
    ///\todo nsevents have timestamps, so why not use them? also, x11 dispatcher have some lasttimestamp field. what was it
    ///used for?

    //lE.mKey=CX11Toolkit::GetKey(lKeySym);
    lE.mMods=mLastModifiers;
    lE.mLocks=CCocoaToolkit::GetLocks(lModifierFlags);
    msg_info() << "mods/locks" << lE.mMods << "/" << lE.mLocks << "\n";
    msg_info() << "keycode:" << [pEvent keyCode] << "\n";
    msg_info() << "characters:\"" << nsstring_to_string([pEvent characters]) << "\"\n";
    msg_info() << "charactersignoring:" << nsstring_to_string([pEvent charactersIgnoringModifiers]) << "\n";
    //lE.mPlainChar=false;
    //lE.mChar=0;
/*
    //Map key to SymKey and maybe char
    if(pE->type==KeyPress)
    {
        char lChar[8];
        int lLen=0;
        
        if(mDisplay->GetXIC())
        {
            Status lStatus;
            lLen=Xutf8LookupString(mDisplay->GetXIC(),pE,lChar,8,
                                                &lKeySym,&lStatus);
            if(lLen)
            {
                lChar[lLen]=0;
                lE.mChar=utf32(lChar)[0];
            }
        }
        else
        {
            lLen=XLookupString(pE,lChar,8,&lKeySym,nullptr);
            lE.mChar=lChar[0];
        }

        lE.mType=CKeyEvent::PRESS;

        if(lE.mChar>=32&&lE.mChar!=127&&!(lE.mMods&(KEYMOD_ALT|KEYMOD_CONTROL)))
        {
            lE.mPlainChar=true;
        }
    }
    else
    {
        lE.mType=CKeyEvent::RELEASE;
    }

*/
//     Qt::KeyboardModifiers modifiers = [self convertKeyModifiers:[nsevent modifierFlags]];
//     NSString *charactersIgnoringModifiers = [nsevent charactersIgnoringModifiers];
// 
//     QChar ch;
//     int keyCode;
//     if ([charactersIgnoringModifiers length] > 0) {
//         // convert the first character into a key code
//         ch = QChar([charactersIgnoringModifiers characterAtIndex:0]);
//         keyCode = [self convertKeyCode:ch];
//     } else {
//         // might be a dead key
//         ch = QChar::ReplacementCharacter;
//         keyCode = Qt::Key_unknown;
//     }
// 
//     // we will send a key event unless the input method sets m_sendKeyEvent to false
//     m_sendKeyEvent = true;
// 
//     QString text;
//     if (eventType == QEvent::KeyPress) {
//         // ignore text for the U+F700-U+F8FF range. This is used by Cocoa when
//         // delivering function keys (e.g. arrow keys, backspace, F1-F35, etc.)
//         if ([charactersIgnoringModifiers length] == 1 && (ch.unicode() < 0xf700 || ch.unicode() > 0xf8ff))
//             text = QCFString::toQString([nsevent characters]);
// 
//         if (m_composingText.isEmpty())
//             m_sendKeyEvent = !QWindowSystemInterface::tryHandleSynchronousShortcutEvent(m_window, timestamp, keyCode, modifiers, text);
// 
//         QObject *fo = QGuiApplication::focusObject();
//         if (m_sendKeyEvent && fo) {
//             QInputMethodQueryEvent queryEvent(Qt::ImEnabled | Qt::ImHints);
//             if (QCoreApplication::sendEvent(fo, &queryEvent)) {
//                 bool imEnabled = queryEvent.value(Qt::ImEnabled).toBool();
//                 Qt::InputMethodHints hints = static_cast<Qt::InputMethodHints>(queryEvent.value(Qt::ImHints).toUInt());
//                 if (imEnabled && !(hints & Qt::ImhDigitsOnly || hints & Qt::ImhFormattedNumbersOnly || hints & Qt::ImhHiddenText)) {
//                     // pass the key event to the input method. note that m_sendKeyEvent may be set to false during this call
//                     [self interpretKeyEvents:[NSArray arrayWithObject:nsevent]];
//                 }
//             }
//         }
//     }
// 
//     if (m_sendKeyEvent && m_composingText.isEmpty())
//         QWindowSystemInterface::handleKeyEvent(m_window, timestamp, QEvent::Type(eventType), keyCode, modifiers, text);
// 
//     m_sendKeyEvent = false;


//    window_manager().OnPhysicalKey(&lE);
}

void CCocoaWindow::DispatchModifier(NSEvent *pEvent)
{
//     ulong timestamp = [nsevent timestamp] * 1000;
    EKeyMod lNewModifiers=CCocoaToolkit::GetMods([pEvent modifierFlags]);

    EKeyMod lDelta=mLastModifiers^lNewModifiers;

    mLastModifiers=lNewModifiers;
    
    static const std::pair<EKeyMod,EKeyCode> lMap[]={
        {   KEYMOD_ALT,KEY_LALT         },
        {   KEYMOD_CONTROL,KEY_LCONTROL },
        {   KEYMOD_SHIFT,KEY_LSHIFT     },
        {   KEYMOD_COMMAND,KEY_COMMAND  },
        {   KEYMOD_NONE,KEY_NONE        }
    };

    for(int i = 0;lMap[i].first!=KEYMOD_NONE;++i)
    {
        if(lDelta&lMap[i].first)
        {
            CKeyEvent lE;
            lE.mType=(lNewModifiers&lMap[i].first)?CKeyEvent::PRESS:CKeyEvent::RELEASE;
            lE.mKey=lMap[i].second;
            lE.mMods=lNewModifiers;
            window_manager().OnPhysicalKey(&lE);
        }
    }
}

/*namespace cocoa*/ } /*namespace gui*/ } /*namespace buola*/ }
